package com.example.administrator.cookman.ui.component;

import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Region;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.view.animation.DecelerateInterpolator;

import com.example.administrator.cookman.R;

/**
 * Created by Administrator on 2017/2/25.
 */

public class SwitchIconView extends AppCompatImageView {
    private static final int DEFAULT_ANIMATION_DURATION = 300;
    private static final float DASH_THICKNESS_PART = 1f / 12f;
    private static final float DEFAULT_DISABLED_ALPHA = .5f;
    private static final float SIN_45 = (float) Math.sin(Math.toRadians(45));

    private final long animationDuration;
    @FloatRange(from = 0f, to = 1f)
    private final float disabledStateAlpha;
    private final int dashXStart;
    private final int dashYStart;
    private final Path clipPath;
    private final int iconTintColor;
    private final int disabledStateColor;
    private final boolean noDash;
    private int dashThickness;
    private int dashLengthXProjection;
    private int dashLengthYProjection;
    private PorterDuffColorFilter colorFilter;
    private final ArgbEvaluator colorEvaluator = new ArgbEvaluator();

    @FloatRange(from = 0f, to = 1f)
    private float fraction = 0f;
    private boolean enabled;

    @NonNull
    private final Paint dashPaint;
    @NonNull
    private final Point dashStart = new Point();
    @NonNull
    private final Point dashEnd = new Point();

    public SwitchIconView(@NonNull Context context) {
        this(context, null);
    }

    public SwitchIconView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SwitchIconView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray array = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.SwitchIconView, 0, 0);

        try {
            iconTintColor = array.getColor(R.styleable.SwitchIconView_si_tint_color, Color.BLACK);
            animationDuration = array.getInteger(R.styleable.SwitchIconView_si_animation_duration, DEFAULT_ANIMATION_DURATION);
            disabledStateAlpha = array.getFloat(R.styleable.SwitchIconView_si_disabled_alpha, DEFAULT_DISABLED_ALPHA);
            disabledStateColor = array.getColor(R.styleable.SwitchIconView_si_disabled_color, iconTintColor);
            enabled = array.getBoolean(R.styleable.SwitchIconView_si_disabled_alpha, true);
            noDash = array.getBoolean(R.styleable.SwitchIconView_si_no_dash, false);
        } finally {
            array.recycle();
        }

        if (disabledStateAlpha < 0f || disabledStateAlpha > 1f) {
            throw new IllegalArgumentException("Wrong value for si_disabled_alpha [" + disabledStateAlpha + "]. "
                    + "Must be value from range [0, 1]");
        }

        colorFilter = new PorterDuffColorFilter(iconTintColor, PorterDuff.Mode.SRC_IN);
        setColorFilter(colorFilter);

        dashXStart = getPaddingLeft();
        dashYStart = getPaddingTop();

        dashPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        dashPaint.setStyle(Paint.Style.STROKE);
        dashPaint.setColor(iconTintColor);

        clipPath = new Path();

        initDashCoordinates();
        setFraction(enabled ? 0f : 1f);
    }

    /**
     * Changes state with animation
     *
     * @param enabled If TRUE - icon is enabled
     */
    public void setIconEnabled(boolean enabled) {
        setIconEnabled(enabled, true);
    }

    /**
     * Changes state
     *
     * @param enabled If TRUE - icon is enabled
     */
    public void setIconEnabled(boolean enabled, boolean animate) {
        if (this.enabled == enabled) return;
        switchState(animate);
    }

    /**
     * Check state
     *
     * @return TRUE if icon is enabled, otherwise FALSE
     */
    public boolean isIconEnabled() {
        return enabled;
    }

    /**
     * Switches icon state with animation
     */
    public void switchState() {
        switchState(true);
    }

    /**
     * Switches icon state
     *
     * @param animate Indicates that state will be changed with or without animation
     */
    public void switchState(boolean animate) {
        float newFraction;
        if (enabled) {
            newFraction = 1f;
        } else {
            newFraction = 0f;
        }
        enabled = !enabled;
        if (animate) {
            animateToFraction(newFraction);
        } else {
            setFraction(newFraction);
            invalidate();
        }
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SwitchIconSavedState savedState = new SwitchIconSavedState(superState);
        savedState.iconEnabled = enabled;
        return savedState;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SwitchIconSavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }
        SwitchIconSavedState savedState = (SwitchIconSavedState) state;
        super.onRestoreInstanceState(savedState.getSuperState());
        enabled = savedState.iconEnabled;
        setFraction(enabled ? 0f : 1f);
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
        super.onSizeChanged(width, height, oldWidth, oldHeight);
        dashLengthXProjection = width - getPaddingLeft() - getPaddingRight();
        dashLengthYProjection = height - getPaddingTop() - getPaddingBottom();
        dashThickness = (int) (DASH_THICKNESS_PART * (dashLengthXProjection + dashLengthYProjection) / 2f);
        dashPaint.setStrokeWidth(dashThickness);
        initDashCoordinates();
        updateClipPath();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (!noDash) {
            drawDash(canvas);
            canvas.clipPath(clipPath, Region.Op.XOR);
        }
        super.onDraw(canvas);
    }

    private void animateToFraction(float toFraction) {
        ValueAnimator animator = ValueAnimator.ofFloat(fraction, toFraction);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                setFraction((float) animation.getAnimatedValue());
            }
        });
        animator.setInterpolator(new DecelerateInterpolator());
        animator.setDuration(animationDuration);
        animator.start();
    }

    private void setFraction(float fraction) {
        this.fraction = fraction;
        updateColor(fraction);
        updateAlpha(fraction);
        updateClipPath();
        postInvalidateOnAnimationCompat();
    }

    private void initDashCoordinates() {
        float delta1 = 1.5f * SIN_45 * dashThickness;
        float delta2 = 0.5f * SIN_45 * dashThickness;
        dashStart.x = (int) (dashXStart + delta2);
        dashStart.y = dashYStart + (int) (delta1);
        dashEnd.x = (int) (dashXStart + dashLengthXProjection - delta1);
        dashEnd.y = (int) (dashYStart + dashLengthYProjection - delta2);
    }

    private void updateClipPath() {
        float delta = dashThickness / SIN_45;
        clipPath.reset();
        clipPath.moveTo(dashXStart, dashYStart + delta);
        clipPath.lineTo(dashXStart + delta, dashYStart);
        clipPath.lineTo(dashXStart + dashLengthXProjection * fraction, dashYStart + dashLengthYProjection * fraction - delta);
        clipPath.lineTo(dashXStart + dashLengthXProjection * fraction - delta, dashYStart + dashLengthYProjection * fraction);
    }

    private void drawDash(Canvas canvas) {
        float x = fraction * (dashEnd.x - dashStart.x) + dashStart.x;
        float y = fraction * (dashEnd.y - dashStart.y) + dashStart.y;
        canvas.drawLine(dashStart.x, dashStart.y, x, y, dashPaint);
    }

    private void updateColor(float fraction) {
        if (iconTintColor != disabledStateColor) {
            final int color = (int) colorEvaluator.evaluate(fraction, iconTintColor, disabledStateColor);
            updateImageColor(color);
            dashPaint.setColor(color);
        }
    }

    private void updateAlpha(float fraction) {
        int alpha = (int) ((disabledStateAlpha + (1f - fraction) * (1f - disabledStateAlpha)) * 255);
        updateImageAlpha(alpha);
        dashPaint.setAlpha(alpha);
    }

    private void updateImageColor(int color) {
        colorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);
        setColorFilter(colorFilter);
    }

    @SuppressWarnings("deprecation")
    private void updateImageAlpha(int alpha) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            setImageAlpha(alpha);
        } else {
            setAlpha(alpha);
        }
    }

    private void postInvalidateOnAnimationCompat() {
        final long fakeFrameTime = 10;
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
            postInvalidateOnAnimation();
        } else {
            postInvalidateDelayed(fakeFrameTime);
        }
    }

    static class SwitchIconSavedState extends BaseSavedState {
        boolean iconEnabled;

        SwitchIconSavedState(Parcelable superState) {
            super(superState);
        }

        private SwitchIconSavedState(Parcel in) {
            super(in);
            final int enabled = in.readInt();
            iconEnabled = enabled == 1;
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(iconEnabled ? 1 : 0);
        }

        public static final Parcelable.Creator<SwitchIconSavedState> CREATOR =
                new Parcelable.Creator<SwitchIconSavedState>() {
                    public SwitchIconSavedState createFromParcel(Parcel in) {
                        return new SwitchIconSavedState(in);
                    }

                    public SwitchIconSavedState[] newArray(int size) {
                        return new SwitchIconSavedState[size];
                    }
                };
    }
}
